use crate::{expr::{Literal, Operator}, Expr};

impl Expr {
    pub fn eliminate(&mut self) {
        match self {
            Expr::Compound(exprs) => {
                exprs.iter_mut().for_each(Expr::eliminate);

                if exprs.len() == 1 {
                    *self = exprs.pop().unwrap()
                }
            }
            Expr::List(exprs) => exprs.iter_mut().for_each(Expr::eliminate),
            Expr::Block(exprs) => exprs.iter_mut().for_each(Expr::eliminate),
            Expr::Operation(op, exprs) => {
                exprs.iter_mut().for_each(Expr::eliminate);

                match op {
                    Operator::Not => {
                        if let Some(Expr::Literal(Literal::Boolean(b))) = exprs.last() {
                            *self = Expr::Literal((!b).into())
                        }
                    }
                    Operator::And => {
                        let mut acc = true;
                        for val in exprs.iter() {
                            if let Some(l) = val.as_literal().and_then(Literal::as_bool) {
                                acc = acc && *l;
                            } else {
                                return;
                            }
                        }
                        *self = Expr::Literal(acc.into())
                    }
                    Operator::Or => {
                        let mut acc = false;
                        for val in exprs.iter() {
                            if let Some(l) = val.as_literal().and_then(Literal::as_bool) {
                                acc = acc || *l;
                            } else {
                                return;
                            }
                        }
                        *self = Expr::Literal(acc.into())
                    }
                    Operator::Add => {
                        let mut it = exprs.iter().rev();
                        if let Some(mut acc) = it
                            .next()
                            .and_then(Expr::as_literal)
                            .map(Literal::clone)
                        {
                            for val in it {
                                if let Some(l) = val.as_literal() {
                                    if !acc.try_add_assign(l) {
                                        return;
                                    }
                                } else {
                                    return;
                                }
                            }
                            *self = Expr::Literal(acc)
                        }
                    }
                    Operator::Sub => {
                        let mut it = exprs.iter().rev();
                        if let Some(mut acc) = it
                            .next()
                            .and_then(Expr::as_literal)
                            .map(Literal::clone)
                        {
                            for val in it {
                                if let Some(l) = val.as_literal() {
                                    if !acc.try_sub_assign(l) {
                                        return;
                                    }
                                } else {
                                    return;
                                }
                            }
                            *self = Expr::Literal(acc)
                        }
                    }
                    Operator::Mul => {
                        let mut it = exprs.iter().rev();
                        if let Some(mut acc) = it
                            .next()
                            .and_then(Expr::as_literal)
                            .map(Literal::clone)
                        {
                            for val in it {
                                if let Some(l) = val.as_literal() {
                                    if !acc.try_mul_assign(l) {
                                        return;
                                    }
                                } else {
                                    return;
                                }
                            }
                            *self = Expr::Literal(acc)
                        }
                    }
                    Operator::Div => {
                        let mut it = exprs.iter().rev();
                        if let Some(mut acc) = it
                            .next()
                            .and_then(Expr::as_literal)
                            .map(Literal::clone)
                        {
                            for val in it {
                                if let Some(l) = val.as_literal() {
                                    if !acc.try_div_assign(l) {
                                        return;
                                    }
                                } else {
                                    return;
                                }
                            }
                            *self = Expr::Literal(acc)
                        }
                    }
                    // TODO: Constant arithmatics.
                    _ => (),
                }
            }
            _ => (),
        }
    }
}

impl Literal {
    pub fn try_add_assign(&mut self, rhs: &Self) -> bool {
        match self {
            Literal::Boolean(l) => match rhs {
                Literal::String(r) => {
                    *self = Literal::String(format!("{l}{r}"));
                    true
                }
                _ => false,
            },
            Literal::Integer(l) => match rhs {
                Literal::Boolean(_) => false,
                Literal::Integer(r) => {
                    *l += r;
                    true
                }
                Literal::Real(r) => {
                    *self = Literal::Real(*l as f32 + r);
                    true
                }
                Literal::String(r) => {
                    *self = Literal::String(format!("{l}{r}"));
                    true
                }
            },
            Literal::Real(l) => match rhs {
                Literal::Boolean(_) => false,
                Literal::Integer(r) => {
                    *l += *r as f32;
                    true
                }
                Literal::Real(r) => {
                    *l += r;
                    true
                }
                Literal::String(r) => {
                    *self = Literal::String(format!("{l}{r}"));
                    true
                }
            },
            Literal::String(l) => match rhs {
                Literal::Boolean(r) => {
                    *l += &r.to_string();
                    true
                }
                Literal::Integer(r) => {
                    *l += &r.to_string();
                    true
                }
                Literal::Real(r) => {
                    *l += &r.to_string();
                    true
                }
                Literal::String(r) => {
                    *l += r;
                    true
                }
            },
        }
    }

    fn try_sub_assign(&mut self, rhs: &Self) -> bool {
        match self {
            Self::Integer(l) => match rhs {
                Self::Integer(r) => {
                    *l -= r;
                    true
                }
                Self::Real(r) => {
                    *self = Self::Real(*l as f32 - *r);
                    true
                }
                _ => false,
            },
            Self::Real(l) => match rhs {
                Self::Integer(r) => {
                    *l -= *r as f32;
                    true
                }
                Self::Real(r) => {
                    *l -= r;
                    true
                }
                _ => false,
            },
            _ => false,
        }
    }

    fn try_mul_assign(&mut self, rhs: &Self) -> bool {
        match self {
            Self::Integer(l) => match rhs {
                Self::Integer(r) => {
                    *l *= r;
                    true
                }
                Self::Real(r) => {
                    *self = Self::Real(*l as f32 * *r);
                    true
                }
                _ => false,
            },
            Self::Real(l) => match rhs {
                Self::Integer(r) => {
                    *l *= *r as f32;
                    true
                }
                Self::Real(r) => {
                    *l *= r;
                    true
                }
                _ => false,
            },
            _ => false,
        }
    }

    fn try_div_assign(&mut self, rhs: &Self) -> bool {
        match self {
            Self::Integer(l) => match rhs {
                Self::Integer(r) => {
                    *l /= r;
                    true
                }
                Self::Real(r) => {
                    *self = Self::Real(*l as f32 / *r);
                    true
                }
                _ => false,
            },
            Self::Real(l) => match rhs {
                Self::Integer(r) => {
                    *l /= *r as f32;
                    true
                }
                Self::Real(r) => {
                    *l /= r;
                    true
                }
                _ => false,
            },
            _ => false,
        }
    }
}